/**
 * @private
 * 断线重连后，延迟5秒重新创建WebSocket连接  rec用来存储延迟请求的代码
 */
let rec;
/**
 * 连接标识 避免重复连接
 * @private
 * @type {boolean}
 */
let isConnect = false;
/**
 * 是否连接中
 * @private
 * @type {boolean}
 */
let isConnecting = false;
/**
 * 连接异常标识, 避免重复连接
 * @private
 * @type {boolean}
 */
let isConnectAError = false;
/**
 * 上线消息
 */
let mqOnlineMsg;

//===================================== 环境 开始 ========================================
// 环境
let env = wx ? 'wx' : 'html';
// 获取window对象
let global = env === 'html' ? window : env === 'wx' ? wx : null;
// webSocket协议
let wsProtocol = "ws";
// 缓存
let cache = isHtml() ? {
  set: (key, value) => {
    localStorage.setItem(key, JSON.stringify(value));
  },
  get: key => JSON.parse(localStorage.getItem(key))
} : isWx() ? {
  set: (key, value) => {
    global.setStorage({
      key,
      data: JSON.stringify(value)
    })
  },
  get: key => JSON.parse(global.getStorageSync(key))
} : null;

function isHtml () {
  return env === 'html'
}
function isWx () {
  return env === 'wx'
}
//===================================== 环境 结束 ========================================


//===================================== 常量 开始 ========================================
/**
 * 消息中心连接缓存key
 * @private
 * @type {string}
 */
let mqConnCacheKey = 'htmqMqConn';

/**
 * 日志级别
 * @private
 * @type {{TRACE: {code: string, value: number}, ERROR: {code: string, value: number}, INFO: {code: string, value: number}, DEBUG: {code: string, value: number}, WARN: {code: string, value: number}}}
 */
let LogLevel = {
  // 异常
  ERROR: {
    code: 'ERROR',
    value: 0
  },
  // 警告
  WARN: {
    code: 'WARN',
    value: 1
  },
  // 常规
  INFO: {
    code: 'INFO',
    value: 2
  },
  // 调试
  DEBUG: {
    code: 'DEBUG',
    value: 3
  },
  // 堆栈跟踪
  TRACE: {
    code: 'TRACE',
    value: 4
  },
};

/**
 * 在线类型
 * @private
 * @type {{SYSTEM: string, MODULE: string, WARD: string, DEVICE: string, DEPT: string, USER: string}}
 */
let OnlineType = {
  // 用户
  USER: 'User',
  // 科室
  DEPT: 'Dept',
  // 病区
  WARD: 'Ward',
  // 设备
  DEVICE: 'Device',
  // 系统
  MODULE: 'Module',
  // 系统
  SYSTEM: 'System'
}

/**
 * 消息类型
 * @private
 * @type {{CALLBACK: string, LOGIN: string, PUBLISH: string, HEARTBEAT: string, EXIT: string}}
 */
let MessageType = {
  // 上线 - 1
  LOGIN: '1',
  // 下线 - 2
  EXIT: '2',
  // 分发消息 - 3
  PUBLISH: '3',
  // 回调 - 4
  CALLBACK: '4',
  // 心跳检测 - 5
  HEARTBEAT: '5'
}

/**
 * 消息发送模式
 * @private
 * @type {{PUB_SUB: string, P2P: string}}
 */
let MessageMode = {
  // p2p 点到点
  P2P: 'P2P',
  // 分发模型
  PUB_SUB: 'PUB_SUB'
}

/**
 * 主题级别
 * @private
 * @type {{COMMON: string, SPECIAL: string, STANDARD: string}}
 */
let TopicLevel = {
  // 特级
  SPECIAL: '90',
  // 标准
  STANDARD: '60',
  // 普通
  COMMON: '30'
}

/**
 * 处理方式
 * @private
 * @type {{MESSAGE: string, CANCEL: string, CUSTOM: string, COMMON: string, PROHIBIT: string, NONE: string, WARN: string, URL: string}}
 */
let HandleType = {
  // 确认取消框
  CANCEL: '90',
  // X禁止确认框
  PROHIBIT: '80',
  // 警告确认
  WARN: '70',
  // 普通确认框
  COMMON: '60',
  // 自动消息框
  MESSAGE: '50',
  // URL跳转
  URL: '40',
  // 自定义
  CUSTOM: '30',
  // 无处理
  NONE: '10'
}

/**
 * 发布方式
 * @private
 * @type {{TASK: string, TRIGGER: string}}
 */
let PublishMode = {
  // 任务触发
  TASK: "Task",
  // 业务触发
  TRIGGER: "Trigger"
}

/**
 * 客户端类型
 * @private
 * @type {{JAVA: string, WEB: string, ANDROID: string, IOS: string}}
 */
let ClientType = {
  JAVA: 'java',
  WEB: 'web',
  ANDROID: 'android',
  IOS: 'ios'
}

/**
 * 生命周期
 * @private
 * @type {{beforeSend: function(), destroyed: function(), beforeReceive: function(MessageEvent), created: function(): Promise<unknown>, beforeDestroy: function(): Promise<unknown>, received: function(ReceiveMessage), beforeCreate: function(): Promise<unknown>, sent: function()}}
 */
let LifeCycle = {
  /**
   * HtmWebSocket创建前
   * @returns {Promise<*>}
   */
  beforeCreate: () => {
    return new Promise((resolve, reject) => {
      // 成功后执行
      resolve();
      // 失败后执行
      reject();
    })
  },
  /**
   * HtmWebSocket创建后
   * @returns {Promise<*>}
   */
  created: () => {
    return new Promise((resolve, reject) => {
      // 成功后执行
      resolve();
      // 失败后执行
      reject();
    })
  },
  /**
   * 接收消息内容处理前
   * @param {MessageEvent} messageEvent 原生MessageEvent对象
   */
  beforeReceive: (messageEvent) => {
    Logger.debug(messageEvent)
  },
  /**
   * 接收消息内容处理
   * @param {ReceiveMessage} receiveMessage 接收的消息对象
   */
  received: (receiveMessage) => {
    Logger.debug(receiveMessage)
  },
  /**
   * 消息发送前
   */
  beforeSend: () => {
  },
  /**
   * 消息发送后
   */
  sent: () => {
  },
  /**
   * HtmWebSocket销毁前
   * @returns {Promise<*>}
   */
  beforeDestroy: () => {
    return new Promise((resolve, reject) => {
      // 成功后执行
      resolve();
      // 失败后执行
      reject();
    })
  },
  /**
   * HtmWebSocket销毁后
   */
  destroyed: () => {
  }
}

/**
 * 日志级别
 * @private
 * @type {{code: string, value: number}}
 */
let loggerLevel = LogLevel.INFO;
//===================================== 常量 结束 ========================================


//===================================== 日志工具 开始 =====================================
/**
 * 日志对象
 * @private
 * @type {{warn: warn, trace: trace, debug: debug, log: info, error: error, info: info}}
 */
const Logger = {
  // trace日志
  trace: (() => {
    // 当前日志级别小等于 trace
    if (LogLevel.TRACE.value > loggerLevel.value) {
      return () => {
      };
    }
    if (typeof process !== 'undefined' && process.env.NODE_ENV === 'development') {
      return console.info.bind(console, ...[
        `%c[${LogLevel.INFO.code}]htm-ws%c${pattern(new Date(), "yyyy-MM-dd HH:mm:ss")}`,
        "color: #ddd; background: #1677ffB6; padding: 3px 3px 3px 6px; border-radius: 14px 0 0 14px;border: 1px solid #1677ff;border-right: none;",
        "color: #333; background: #DDDDDDDD; padding: 3px 6px 3px 3px; border-radius: 0 14px 14px 0;border: 1px solid #DDDDDD;border-left: none;",
      ]);
    }
    return console.trace.bind(console, ...[
      `[${LogLevel.TRACE.code}]htm-ws ${pattern(new Date(), "yyyy-MM-dd HH:mm:ss")}`,
    ])
  })(),
  // debug日志
  debug: (() => {
    // 当前日志级别小等于 info
    if (LogLevel.DEBUG.value > loggerLevel.value) {
      return () => {
      };
    }
    if (typeof process !== 'undefined' && process.env.NODE_ENV === 'development') {
      return console.debug.bind(console, ...[
        `%c[${LogLevel.DEBUG.code}]htm-ws%c${pattern(new Date(), "yyyy-MM-dd HH:mm:ss")}`,
        "color: #ddd; background: #1677ffB6; padding: 3px 3px 3px 6px; border-radius: 14px 0 0 14px;border: 1px solid #1677ff;border-right: none;",
        "color: #333; background: #AAAAAACC; padding: 3px 6px 3px 3px; border-radius: 0 14px 14px 0;border: 1px solid #AAAAAA;border-left: none;",
      ]);
    }
    return console.debug.bind(console, ...[
      `[${LogLevel.DEBUG.code}]htm-ws ${pattern(new Date(), "yyyy-MM-dd HH:mm:ss")}`,
    ]);
  })(),
  // info日志
  info: (() => {
    // 当前日志级别小等于 info
    if (LogLevel.INFO.value > loggerLevel.value) {
      return () => {
      };
    }
    if (typeof process !== 'undefined' && process.env.NODE_ENV === 'development') {
      return console.info.bind(console, ...[
        `%c[${LogLevel.INFO.code}]htm-ws%c${pattern(new Date(), "yyyy-MM-dd HH:mm:ss")}`,
        "color: #ddd; background: #1677ffB6; padding: 3px 3px 3px 6px; border-radius: 14px 0 0 14px;border: 1px solid #1677ff;border-right: none;",
        "color: #333; background: #DDDDDDDD; padding: 3px 6px 3px 3px; border-radius: 0 14px 14px 0;border: 1px solid #DDDDDD;border-left: none;",
      ]);
    }
    return console.info.bind(console, ...[
      `[${LogLevel.INFO.code}]htm-ws ${pattern(new Date(), "yyyy-MM-dd HH:mm:ss")}`,
    ])
  })(),
  // warn日志
  warn: (() => {
    // 当前日志级别小等于 warn
    if (LogLevel.WARN.value > loggerLevel.value) {
      return () => {
      };
    }
    if (typeof process !== 'undefined' && process.env.NODE_ENV === 'development') {
      return console.warn.bind(console, ...[
        `%c[${LogLevel.WARN.code}]htm-ws%c${pattern(new Date(), "yyyy-MM-dd HH:mm:ss")}`,
        "color: #ddd; background: #1677ffB6; padding: 3px 3px 3px 6px; border-radius: 14px 0 0 14px;border: 1px solid #1677ff;border-right: none;",
        "color: #ddd; background: #FFB800CC; padding: 3px 6px 3px 3px; border-radius: 0 14px 14px 0;border: 1px solid #FFB800;border-left: none;",
      ]);
    }
    return console.warn.bind(console, ...[
      `%[${LogLevel.WARN.code}]htm-ws ${pattern(new Date(), "yyyy-MM-dd HH:mm:ss")}`,
    ]);
  })(),
  // error日志
  error: (() => {
    // 当前日志级别小等于 error
    if (LogLevel.ERROR.value > loggerLevel.value) {
      return () => {
      };
    }
    if (typeof process !== 'undefined' && process.env.NODE_ENV === 'development') {
      return console.error.bind(console, ...[
        `%c[${LogLevel.ERROR.code}]htm-ws%c${pattern(new Date(), "yyyy-MM-dd HH:mm:ss")}`,
        "color: #ddd; background: #1677ffB6; padding: 2px 3px 2px 6px; border-radius: 14px 0 0 14px;border: 1px solid #1677ff;border-right: none;",
        "color: #CF2121; background: #FF8A8ACC; padding: 2px 6px 2px 3px; border-radius: 0 14px 14px 0;border: 1px solid #FF8A8A;border-left: none;",
      ]);
    }
    return console.error.bind(console, ...[
      `[${LogLevel.ERROR.code}]htm-ws ${pattern(new Date(), "yyyy-MM-dd HH:mm:ss")}`,
    ]);
  })(),
};
//===================================== 日志工具 结束 =====================================


//===================================== 对外API 开始 =====================================
/**
 * WebSocket通讯工具
 * 提供快捷连接操作webSocket方法
 * @public
 * @class HtmWebSocket
 * @author lm
 * @version v5.0
 * @copyright 南京海泰医疗信息系统有限公司
 * @team 智慧物联产品部
 * @date 2024/01/22　11:00
 */
class HtmWebSocket {

  /**
   * 日志级别
   * @public
   * @type {{TRACE: {code: string, value: number}, ERROR: {code: string, value: number}, INFO: {code: string, value: number}, DEBUG: {code: string, value: number}, WARN: {code: string, value: number}}}
   */
  static LogLevel = LogLevel;

  /**
   * 在线类型
   * @public
   * @type {{SYSTEM: string, MODULE: string, WARD: string, DEVICE: string, DEPT: string, USER: string}}
   */
  static OnlineType = OnlineType;

  /**
   * 消息类型
   * @public
   * @type {{CALLBACK: string, LOGIN: string, PUBLISH: string, HEARTBEAT: string, EXIT: string}}
   */
  static MessageType = MessageType;

  /**
   * 消息发送模式
   * @public
   * @type {{PUB_SUB: string, P2P: string}}
   */
  static MessageMode = MessageMode;

  /**
   * 主题级别
   * @public
   * @type {{COMMON: string, SPECIAL: string, STANDARD: string}}
   */
  static TopicLevel = TopicLevel;

  /**
   * 处理方式
   * @public
   * @type {{MESSAGE: string, CANCEL: string, CUSTOM: string, COMMON: string, PROHIBIT: string, NONE: string, WARN: string, URL: string}}
   */
  static HandleType = HandleType;

  /**
   * 发布方式
   * @public
   * @type {{TASK: string, TRIGGER: string}}
   */
  static PublishMode = PublishMode;

  /**
   * 客户端类型
   * @type {{JAVA: string, WEB: string, ANDROID: string, IOS: string}}
   */
  static ClientType = ClientType;

  /**
   * 生命周期
   * @public
   * @type {{beforeSend: function(), destroyed: function(), beforeReceive: function(MessageEvent), created: function(): Promise<unknown>, beforeDestroy: function(): Promise<unknown>, received: function(ReceiveMessage), beforeCreate: function(): Promise<unknown>, sent: function()}}
   */
  static LifeCycle = LifeCycle;

  /**
   * 获取消息流水号
   * @public
   * @returns {string} 消息流水号
   */
  static getMsgSn() {
    return uuid().replaceAll('-', '');
  }

  /**
   * 创建消息连接配置
   * @param subscriber 订阅者
   * @param subscriberType 订阅者类型
   * @param appCode 应用编号
   * @param appVer 应用版本
   * @param clientType 客户端类型（为空时，默认设为Web）
   * @param lifeCycle 生命周期（可空）
   * @param lifeCycle.beforeCreate HtmWebSocket创建前
   * @param lifeCycle.created HtmWebSocket创建后
   * @param lifeCycle.beforeReceive 接收消息内容处理前
   * @param lifeCycle.received 接收消息内容处理
   * @param lifeCycle.beforeSend 消息发送前
   * @param lifeCycle.sent 消息发送后
   * @param lifeCycle.beforeDestroy HtmWebSocket销毁前
   * @param lifeCycle.destroyed HtmWebSocket销毁后
   */
  static createMqConnectConfig(subscriber, subscriberType, appCode, appVer, clientType, lifeCycle) {
    // 获取消息中心连接地址信息
    let mqConn = cache.get(mqConnCacheKey);
    let result = {
      // 消息连接信息
      mqConn: mqConn,
      // 上线消息
      onlineMsg: {
        data: {
          // 终端类型
          clientType: clientType || ClientType.WEB,
          // 订阅人
          subscriber: subscriber,
          // 订阅人类型（用户:User、设备:Device、系统:System）
          subscriberType: subscriberType,
          // 应用编码
          appCode: appCode,
          // 应用版本
          appVer: appVer,
          // h5应用编码
          h5AppCode: '',
          // h5应用版本
          h5AppVer: '',
          // 设备id
          deviceId: 'WebDevice',
          // IP地址
          ip: '0.0.0.0',
          // mac地址
          mac: '',
          // 上线时间 yyyyMMddHHmmss
          onlineTime: pattern()
        },
        messageType: MessageType.LOGIN
      },
      lifeCycle
    }
    // if (htm.isAndroid() === "Y") {
    //   htm.getSystemInfo({
    //     success: function (res) {
    //       result.onlineMsg.data.ip = res.ip;
    //     },
    //   });
    // }
    // htm.getDeviceInfo({
    //   success: function (res) {
    //     result.onlineMsg.data.deviceId = res.deviceId;
    //     if(subscriberType === OnlineType.DEVICE){
    //       result.onlineMsg.data.subscriber = res.deviceId;
    //     }
    //   },
    //   fail: ()=>{
    //     result.onlineMsg.data.subscriber = result.onlineMsg.data.deviceId;
    //   }
    // });
    return result;
  }

  /**
   * 创建用户消息连接配置
   * @param userId 用户ID
   * @param appCode 应用编号
   * @param appVer 应用版本
   * @param lifeCycle 生命周期（可空）
   * @param lifeCycle.beforeCreate HtmWebSocket创建前
   * @param lifeCycle.created HtmWebSocket创建后
   * @param lifeCycle.beforeReceive 接收消息内容处理前
   * @param lifeCycle.received 接收消息内容处理
   * @param lifeCycle.beforeSend 消息发送前
   * @param lifeCycle.sent 消息发送后
   * @param lifeCycle.beforeDestroy HtmWebSocket销毁前
   * @param lifeCycle.destroyed HtmWebSocket销毁后
   */
  static createUserMqConnectConfig(userId, appCode, appVer, lifeCycle) {
    return HtmWebSocket.createMqConnectConfig(
        userId,
        OnlineType.USER,
        appCode,
        appVer,
        ClientType.WEB,
        lifeCycle
    );
  }

  /**
   * 创建科室消息连接配置
   * @param deptCode 科室编号
   * @param appCode 应用编号
   * @param appVer 应用版本
   * @param lifeCycle 生命周期（可空）
   * @param lifeCycle.beforeCreate HtmWebSocket创建前
   * @param lifeCycle.created HtmWebSocket创建后
   * @param lifeCycle.beforeReceive 接收消息内容处理前
   * @param lifeCycle.received 接收消息内容处理
   * @param lifeCycle.beforeSend 消息发送前
   * @param lifeCycle.sent 消息发送后
   * @param lifeCycle.beforeDestroy HtmWebSocket销毁前
   * @param lifeCycle.destroyed HtmWebSocket销毁后
   */
  static createDeptMqConnectConfig(deptCode, appCode, appVer, lifeCycle) {
    return HtmWebSocket.createMqConnectConfig(
        deptCode,
        OnlineType.DEPT,
        appCode,
        appVer,
        ClientType.WEB,
        lifeCycle
    );
  }

  /**
   * 创建病区消息连接配置
   * @param wardCode 病区编号
   * @param appCode 应用编号
   * @param appVer 应用版本
   * @param lifeCycle 生命周期（可空）
   * @param lifeCycle.beforeCreate HtmWebSocket创建前
   * @param lifeCycle.created HtmWebSocket创建后
   * @param lifeCycle.beforeReceive 接收消息内容处理前
   * @param lifeCycle.received 接收消息内容处理
   * @param lifeCycle.beforeSend 消息发送前
   * @param lifeCycle.sent 消息发送后
   * @param lifeCycle.beforeDestroy HtmWebSocket销毁前
   * @param lifeCycle.destroyed HtmWebSocket销毁后
   */
  static createWardMqConnectConfig(wardCode, appCode, appVer, lifeCycle) {
    return HtmWebSocket.createMqConnectConfig(
        wardCode,
        OnlineType.WARD,
        appCode,
        appVer,
        ClientType.WEB,
        lifeCycle
    );
  }

  /**
   * 创建设备消息连接配置
   * @param appCode 应用编号
   * @param appVer 应用版本
   * @param lifeCycle 生命周期（可空）
   * @param lifeCycle.beforeCreate HtmWebSocket创建前
   * @param lifeCycle.created HtmWebSocket创建后
   * @param lifeCycle.beforeReceive 接收消息内容处理前
   * @param lifeCycle.received 接收消息内容处理
   * @param lifeCycle.beforeSend 消息发送前
   * @param lifeCycle.sent 消息发送后
   * @param lifeCycle.beforeDestroy HtmWebSocket销毁前
   * @param lifeCycle.destroyed HtmWebSocket销毁后
   */
  static createDeviceMqConnectConfig(appCode, appVer, lifeCycle) {
    return HtmWebSocket.createMqConnectConfig(
        null,
        OnlineType.DEVICE,
        appCode,
        appVer,
        ClientType.WEB,
        lifeCycle
    );
  }

  /**
   * 创建系统消息连接配置
   * @param appCode 应用编号
   * @param appVer 应用版本
   * @param lifeCycle 生命周期（可空）
   * @param lifeCycle.beforeCreate HtmWebSocket创建前
   * @param lifeCycle.created HtmWebSocket创建后
   * @param lifeCycle.beforeReceive 接收消息内容处理前
   * @param lifeCycle.received 接收消息内容处理
   * @param lifeCycle.beforeSend 消息发送前
   * @param lifeCycle.sent 消息发送后
   * @param lifeCycle.beforeDestroy HtmWebSocket销毁前
   * @param lifeCycle.destroyed HtmWebSocket销毁后
   */
  static createSystemMqConnectConfig(appCode, appVer, lifeCycle) {
    return HtmWebSocket.createMqConnectConfig(
      appCode+appVer,
      OnlineType.DEVICE,
      appCode,
      appVer,
      ClientType.WEB,
      lifeCycle
    );
  }

  /**
   * 设置日志级别
   * @example
   * // 日志级别设置为异常
   * HtmWebSocket.setLogLevel(HtmWebSocket.LogLevel.ERROR)
   * // 日志级别设置为调试
   * HtmWebSocket.setLogLevel(HtmWebSocket.LogLevel.DEBUG)
   * @public
   * @param {{code: string, value: number}} logLevel
   */
  static setLogLevel(logLevel) {
    if (!logLevel) {
      loggerLevel = LogLevel.INFO;
    }
    switch (logLevel.value) {
      case LogLevel.ERROR.value:
        loggerLevel = LogLevel.ERROR
        break;
      case LogLevel.WARN.value:
        loggerLevel = LogLevel.WARN
        break;
      case LogLevel.INFO.value:
        loggerLevel = LogLevel.INFO;
        break;
      case LogLevel.DEBUG.value:
        loggerLevel = LogLevel.DEBUG;
        break;
      case LogLevel.TRACE.value:
        loggerLevel = LogLevel.TRACE;
        break;
      default:
        loggerLevel = LogLevel.INFO;
    }
  }

  /**
   * 设置生命周期
   * @example
   * // 设置销毁后执行的函数
   * HtmWebSocket.setLifeCycle({destroyed: function(){console.log('htmWebSocket销毁完毕');}})
   * @param beforeCreate HtmWebSocket创建前
   * @param created HtmWebSocket创建后
   * @param beforeReceive 接收消息内容处理前
   * @param received 接收消息内容处理
   * @param beforeSend 消息发送前
   * @param sent 消息发送后
   * @param beforeDestroy HtmWebSocket销毁前
   * @param destroyed HtmWebSocket销毁后
   */
  static setLifeCycle({
                        beforeCreate = HtmWebSocket.LifeCycle.beforeCreate,
                        created = HtmWebSocket.LifeCycle.created,
                        beforeReceive = HtmWebSocket.LifeCycle.beforeReceive,
                        received = HtmWebSocket.LifeCycle.received,
                        beforeSend = HtmWebSocket.LifeCycle.beforeSend,
                        sent = HtmWebSocket.LifeCycle.sent,
                        beforeDestroy = HtmWebSocket.LifeCycle.beforeDestroy,
                        destroyed = HtmWebSocket.LifeCycle.destroyed
                      } = {}) {
    HtmWebSocket.LifeCycle.beforeCreate = beforeCreate;
    HtmWebSocket.LifeCycle.created = created;
    HtmWebSocket.LifeCycle.beforeReceive = beforeReceive;
    HtmWebSocket.LifeCycle.received = received;
    HtmWebSocket.LifeCycle.beforeSend = beforeSend;
    HtmWebSocket.LifeCycle.sent = sent;
    HtmWebSocket.LifeCycle.beforeDestroy = beforeDestroy;
    HtmWebSocket.LifeCycle.destroyed = destroyed;
  }

  /**
   * 设置mq连接信息
   * @example HtmWebSocket.setMqConn({ip: "192.168.0.159", port: '7788'});
   * @param config 连接信息
   * @param {String} config.ip 服务IP
   * @param {String} config.port 服务端口
   */
  static setMqConn(config) {
    cache.set(mqConnCacheKey, {
      ip: config.ip,
      port: config.port
    });
  }

  /**
   * 更新重连配置
   * @param reconnectSec 单次重连间隔
   * @param intervalSec 每轮重连间隔
   * @param tryMaxNum 每轮最大重连数
   */
  static updateReconnectConfig({
                                 reconnectSec = ReconnectConfig.smallReConnectTime / 1000,
                                 intervalSec = ReconnectConfig.bigReConnectTime / 1000,
                                 tryMaxNum = ReconnectConfig.reConnectMaxCount
                               } = {}) {
    ReconnectConfig.smallReConnectTime = reconnectSec * 1000;
    ReconnectConfig.bigReConnectTime = intervalSec * 1000;
    ReconnectConfig.reConnectMaxCount = tryMaxNum
  }

  /**
   * 启动HtmWebSocket连接
   * @param connConfig 连接配置
   * @param connConfig.mqConn 消息连接信息
   * @param connConfig.onlineMsg 上线消息
   * @param connConfig.lifeCycle 生命周期
   * @param isWss 是否已wss协议连接（不传时，自动根据访问的协议去查找）
   */
  static start(connConfig, isWss) {
    if(isWss) {
      wsProtocol = 'wss'
    }
    isConnecting = true;
    // 设置生命周期
    if(connConfig.lifeCycle){
      HtmWebSocket.setLifeCycle({
        beforeCreate: connConfig.lifeCycle.beforeCreate || HtmWebSocket.LifeCycle.beforeCreate,
        created: connConfig.lifeCycle.created || HtmWebSocket.LifeCycle.created,
        beforeReceive: connConfig.lifeCycle.beforeReceive || HtmWebSocket.LifeCycle.beforeReceive,
        received: connConfig.lifeCycle.received || HtmWebSocket.LifeCycle.received,
        beforeSend: connConfig.lifeCycle.beforeSend || HtmWebSocket.LifeCycle.beforeSend,
        sent: connConfig.lifeCycle.sent || HtmWebSocket.LifeCycle.sent,
        beforeDestroy: connConfig.lifeCycle.beforeDestroy || HtmWebSocket.LifeCycle.beforeDestroy,
        destroyed: connConfig.lifeCycle.destroyed || HtmWebSocket.LifeCycle.destroyed
      });
    }
    mqOnlineMsg = connConfig.onlineMsg;
    try {
      HtmWebSocket.LifeCycle.beforeCreate().catch(err => {
        Logger.error(err);
      }).finally(() => {
        Logger.info("开始初始化，initHtmWebSocket");
        //初始化websocket连接
        initHtmWebSocket();
      });
    } catch (e) {
      Logger.error("尝试创建连接失败", e);
      //如果无法连接上webSocket 那么重新连接！可能会因为服务器重新部署，或者短暂断网等导致无法创建连接
      reConnect();
    }
  }

  /**
   * 发送消息
   * @param params 参数
   * @param params.topicCode 主题编码
   * @param {String} params.publishMode 发布方式
   * @param {Array} params.msg 消息数据列表
   * @param params.msg.tag 目标
   * @param params.msg.data 数据
   * @param {Function} params.callback 此消息回调
   */
  static send(params) {
    if(!params.publishMode){
      params.publishMode = PublishMode.TRIGGER;
    }
    let mqMessage = params.msg
    mqMessage.msgSn = HtmWebSocket.getMsgSn();
    globalCallback.set(mqMessage.msgSn, params.callback)
    delete params.callback;
    let sendMsgObj = {
      data: {
        ...params
      },
      messageType: HtmWebSocket.MessageType.PUBLISH
    }
    sendMsg(sendMsgObj);
  }

  /**
   * 发送消息
   * @param params 参数
   * @param params.topicCode 主题编码
   * @param {String} params.publishMode 发布方式
   * @param {Array} params.msgList 消息数据列表
   * @param params.msgList.tag 目标
   * @param params.msgList.data 数据
   * @param {Function} params.callback 此消息回调
   */
  static sendList(params) {
    if(!params.publishMode){
      params.publishMode = PublishMode.TRIGGER;
    }
    // 记录回调
    for (let mqMessage of params.msgList) {
      if(!mqMessage.msgSn){
        mqMessage.msgSn = HtmWebSocket.getMsgSn();
      }
      globalCallback.set(mqMessage.msgSn, params.callback)
    }
    delete params.callback;
    let sendMsgObj = {
      data: {
        ...params
      },
      messageType: HtmWebSocket.MessageType.PUBLISH
    }
    sendMsg(sendMsgObj);
  }

  /**
   * 处理消息回调
   * @public
   * @param msgSn 消息流水号
   * @param processStatus 处理状态
   * @param processTime 处理时间（yyyyMMddHHmmss，可空）
   */
  static sendHandleCallbackMsg(msgSn, processStatus, processTime) {
    sendMsg({
      data: {
        msgSn,
        processStatus,
        processTime
      },
      messageType: HtmWebSocket.MessageType.CALLBACK
    });
  }

  /**
   * 关闭连接
   * @public
   */
  static closeWebSocket(isActive = true) {
    isActive && Logger.info("主动断开连接")
    heartConfig.stop();
    isConnectAError = false;
    HtmWs.isConSuccess() && HtmWs.closeWs();
    HtmWebSocket.LifeCycle.destroyed();
  };

}
//===================================== 对外API 结束 =====================================


//===================================== 工具函数 开始 =====================================
/**
 * 是否为空
 * @private
 * @param content 内容
 * @returns {boolean} 是否为空
 */
function isBlank(content) {
  return !content || content === 'null' || content === ' ' || content === [] || content === 'undefined' || typeof (content) === 'undefined'
}

/**
 * 将时间转为指定格式    <br/>
 * 栗子：              <br/>
 * pattern() => 20231130163059   <br/>
 * pattern(new Date()) => 20231130163059   <br/>
 * pattern(new Date(), 'yyyyMMdd') => 20231130   <br/>
 * @private
 * @param date 时间对象
 * @param format 指定格式（yyyy:年份, MM:月份, dd:日 HH:小时 mm:分钟 ss:秒）
 * @returns {*}
 */
function pattern(date = new Date(), format = 'yyyyMMddHHmmss') {
  let o = {
    //月份
    "M+": date.getMonth() + 1,
    //日
    "d+": date.getDate(),
    //小时
    "h+": date.getHours() % 12 === 0 ? 12 : date.getHours() % 12,
    //小时
    "H+": date.getHours(),
    //分
    "m+": date.getMinutes(),
    //秒
    "s+": date.getSeconds(),
    //季度
    "q+": Math.floor((date.getMonth() + 3) / 3),
    //毫秒
    "S": date.getMilliseconds()
  };
  let week = {
    "0": "/u65e5",
    "1": "/u4e00",
    "2": "/u4e8c",
    "3": "/u4e09",
    "4": "/u56db",
    "5": "/u4e94",
    "6": "/u516d"
  };
  let re = /(y+)/;
  let t;
  if (re.test(format)) {
    t = re.exec(format)[1];
    format = format.replace(t, (date.getFullYear() + "").substring(4 - t));
  }
  re = /(E+)/;
  if (re.test(format)) {
    t = re.exec(format)[1];
    format = format.replace(t, ((t > 1) ? (t > 2 ? "/u661f/u671f" : "/u5468") : "") + week[date.getDay() + ""]);
  }
  for (let k in o) {
    re = new RegExp("(" + k + ")");
    if (re.test(format)) {
      t = re.exec(format)[1];
      format = format.replace(t, (t.length === 1) ? (o[k]) : (("00" + o[k]).substring(("" + o[k]).length)));
    }
  }
  return format;
}

/**
 * 生成UUID
 * @private
 * @returns {string}
 */
function uuid() {
  let s = [];
  let hexDigits = "0123456789abcdef";
  for (let i = 0; i < 36; i++) {
    s[i] = hexDigits.substr(Math.floor(Math.random() * 0x10), 1);
  }
  s[14] = "4";  // bits 12-15 of the time_hi_and_version field to 0010
  s[19] = hexDigits.substr((s[19] & 0x3) | 0x8, 1);  // bits 6-7 of the clock_seq_hi_and_reserved to 01
  s[8] = s[13] = s[18] = s[23] = "-";
  return s.join("");
}
//===================================== 工具函数 结束 =====================================

//===================================== 配置对象 开始 =====================================

/**
 * 重连配置
 * @private
 * @type {{smallReConnectTime: number, tryCount: number, reConnectMaxCount: number, bigReConnectTime: number}}
 */
const ReconnectConfig = {
  // 单轮重连间隔(8s)
  smallReConnectTime: 8000,
  // 每轮重连间隔(60s)
  bigReConnectTime: 60000,
  // 重连单轮最大重试次数
  reConnectMaxCount: 5,
  // 重连单轮重试次数
  tryCount: 0
}

/**
 * 心跳配置
 * @private
 * @type {{checkMsg: {messageType: string}, stop: heartConfig.stop, timeoutObj: null, start: heartConfig.start, reset: heartConfig.reset, timeout: number}}
 */
const heartConfig = {
  //每段时间发送一次心跳包 这里设置为10s
  timeout: 10000,
  //延时发送消息对象（启动心跳新建这个对象，收到消息后重置对象）
  timeoutObj: null,
  // 心跳检测内容
  checkMsg: {
    messageType: HtmWebSocket.MessageType.HEARTBEAT
  },
  /**
   * 启动心跳检测
   * @private
   */
  start: function () {
    // 开启定时器，定时发送心跳检测消息
    heartConfig.timeoutObj = setInterval(function () {
      // 处于连接状态，则发送检测消息
      if (HtmWs.isConSuccess()) {
        sendMsg(heartConfig.checkMsg);
      }
    }, heartConfig.timeout);
  },
  /**
   * 重置心跳检测
   * @private
   */
  reset: function () {
    Logger.debug("心跳检测重置");
    // 清除定时器, 中止心跳检测
    clearInterval(heartConfig.timeoutObj);
    // 重新开启心跳检测
    heartConfig.start();
  },
  /**
   * 停止心跳检测
   * @private
   */
  stop: function () {
    Logger.debug("心跳检测停止");
    // 清除定时器, 中止心跳检测
    clearInterval(heartConfig.timeoutObj);
  }
};

//===================================== 配置对象 结束 =====================================

//===================================== 消息基础 开始 =====================================
/**
 * 接收消息对象
 * @private
 * @type {{topicCode: string, data: {}, handleType: string, topicLevel: string, messageId: string, tag, topicTitle: string}}
 */
class ReceiveMessage {
  /**
   * 消息编号
   */
  static msgSn;
  /**
   * 处理应用编码
   */
  static processAppCode;
  /**
   * 处理消息应用版本
   */
  static processAppVer;
  /**
   * 处理消息服务api
   */
  static processServerApi;
  /**
   * 处理消息应用名称
   */
  static processAppName;
  /**
   * 处理消息应用图标
   */
  static processAppIcon;
  /**
   * 处理参数
   */
  static handleParamList;
  /**
   * 生产者
   */
  static producer;
  /**
   * 住院IN、门诊OUT、急诊EMER、科研scientific,research(SR)、其它OTHER等分组类型
   */
  static inoutFlag;
  /**
   * 消息时间 yyyyMMddHHmmss
   */
  static messageTime;
  /**
   * 主题流水号
   */
  static topicSn;
  /**
   * 主题编号
   */
  static topicCode;
  /**
   * 主题名称
   */
  static topicName;
  /**
   * 主题等级
   */
  static topicLevel;
  /**
   * 消息处理类型
   */
  static handleType;
  /**
   * 是否使用消息模板
   */
  static useTemplateFlag ;
  /**
   * 是否处理回调
   */
  static handleCallbackFlag;
  /**
   * 消息内容（使用模板时取此字段）
   */
  static msgContent;
  /**
   * 消息对象（不使用模板时取此字段）
   */
  static msgBody;
  /**
   * 已读标志
   */
  static readFlag;
  /**
   * 处理状态
   */
  static processStatus;
  /**
   * 处理状态名称
   */
  static processStatusName;
  /**
   * 颜色
   */
  static colorHex;
  /**
   * 执行动作显示名称
   */
  static actionShortName;
}

/**
 * 发送消息对应的回调
 * @private
 * @type {Map<any, any>}
 */
let globalCallback = new Map();

/**
 * 重连函数
 * @private
 */
let reConnect = () => {
  ReconnectConfig.tryCount++;
  Logger.debug("尝试重新连接-", ReconnectConfig.tryCount);
  // [非异常断开]或[已经连接]
  if (HtmWs.isConSuccess()) {
    // 尝试次数清空
    ReconnectConfig.tryCount = 0;
    return;
  }
  // 如果总尝试次数大等于单轮最大尝试次数，则休眠一段时间
  if (ReconnectConfig.tryCount >= ReconnectConfig.reConnectMaxCount) {
    Logger.debug(`尝试次数用尽[${ReconnectConfig.reConnectMaxCount}], 休眠[${ReconnectConfig.bigReConnectTime}]s后重试!`);
    // 尝试次数清空
    ReconnectConfig.tryCount = 0;
    // 等待一段时间后重新尝试连接
    setTimeout(() => {
      // 不论成功与否，都重新开始新一轮的连接尝试
      reConnect();
    }, ReconnectConfig.bigReConnectTime);
    return;
  }
  // 重连的timeout存在则进行清除
  rec && clearTimeout(rec);
  // x秒后执行重连, 避免过多次过频繁请求重连
  rec = setTimeout(function () {
    // 初始化HtmWebSocket
    (!isConnectAError) && initHtmWebSocket();
  }, ReconnectConfig.smallReConnectTime);
};

/**
 * 初始化websocket
 * @private
 */
function initHtmWebSocket() {
  // 未连接失败, 跳过
  if (!HtmWs.isConFail()) {
    return;
  }
  let protocol = null;
  if(isHtml()) {
    protocol = global.location.protocol;
    wsProtocol = protocol === 'http:' ? 'ws' : 'wss';
  }

  let mqConnCfg = cache.get(mqConnCacheKey);
  if(!mqConnCfg || !mqConnCfg.ip || !mqConnCfg.port){
    Logger.error("消息中心连接配置未设置");
    return ;
  }
  // ws地址
  const wsUri = `${wsProtocol}://${mqConnCfg.ip}:${mqConnCfg.port}/websocket`;
  Logger.debug('连接地址:', wsUri)
  HtmWs.getInstance(mqOnlineMsg, new WebSocket(wsUri));
}

/**
 * 数据接收预处理
 * @private
 * @param res.data.messageType 消息类型
 * @param res.data.data 数据
 * @param res.data.data.replyMsgSn 回复消息流水号
 * @param res.data.data.replyBody 回复内容
 */
function webSocketOnMessage(res) {
  // 消息数据
  let msgData = res.data;
  if (typeof (msgData) == 'string') {
    msgData = JSON.parse(msgData);
  }
  if (msgData.messageType === HtmWebSocket.MessageType.HEARTBEAT) {
    // 心跳检测，跳过
  } else if (msgData.messageType === HtmWebSocket.MessageType.PUBLISH) {
    // 消息分发
    HtmWebSocket.LifeCycle.received(msgData.data);
  } else if (msgData.messageType === HtmWebSocket.MessageType.CALLBACK) {
    // 使用回复消息id
    let replyMsgSn = msgData.data.replyMsgSn
    // 回调消息
    globalCallback.get(replyMsgSn)(msgData.data.replyBody);
  } else {
    // 未定义的消息类型, 暂不处理
  }
}

if(isHtml()) {
  /**
   * 页面刷新，关闭连接。
   * @private
   */
  global.onbeforeunload = function () {
    Logger.debug("页面刷新, 关闭连接");
    HtmWebSocket.closeWebSocket(false);
  }
}

/**
 * 发送上线消息
 * @private
 * @param msgData
 * @param callback
 */
const sendLoginMsg = (msgData, callback) => {
  globalCallback.set("Login", callback);
  sendMsg(msgData);
}

/**
 * 发送消息
 * @private
 * @param msgData 消息内容
 */
const sendMsg = (msgData) => {
  // 非连接中且未初始化, 则创建连接
  if (!isConnecting && !HtmWs.isInit()) {
    Logger.error('HtmWebSocket未启动, 请调用HtmWebSocket.start后，重试发送消息!');
    initHtmWebSocket();
    return ;
  }
  HtmWebSocket.LifeCycle.beforeSend();
  if (HtmWs.isConSuccess()) {
    // 已连接
    HtmWs.getWebsocket().send(JSON.stringify(msgData))
    HtmWebSocket.LifeCycle.sent();
  } else if (HtmWs.isConnecting()) {
    Logger.warn('正在连接中, 2s后自动重新尝试发送消息.')
    // 正在连接中, 等待2s后重新调用
    setTimeout(() => {
      sendMsg(msgData)
    }, 2000)
  } else {
    Logger.warn('连接未开启, 2s后自动重新尝试发送消息.')
    // 若未开启 ，则等待2s后重新调用
    setTimeout(() => {
      sendMsg(msgData)
    }, 2000)
  }
}

//===================================== 消息基础 结束 =====================================

//===================================== 单例对象 开始 =====================================
/**
 * HtmWs类
 * @private
 */
class HtmWs {

  /**
   * 实例
   * @type {Object}
   */
  static instance;

  /**
   * 构造函数
   * @param onlineMsg
   * @param ws
   */
  constructor(onlineMsg, ws) {
    this.websocket = ws;
    if (!this.websocket) {
      return;
    }
    // 接收消息的回调
    this.websocket.onmessage = function (e) {
      // 传递消息对象MessageEvent, 并进行深度拷贝, 避免被误改
      HtmWebSocket.LifeCycle.beforeReceive(Object.assign({}, e));
      webSocketOnMessage(e)
    }
    // 连接关闭回调
    this.websocket.onclose = function (e) {
      HtmWebSocket.LifeCycle.beforeDestroy().catch(err => {
        Logger.error(err);
      }).finally(() => {
        //断开后修改标识
        isConnect = false;
        heartConfig.stop()
        Logger.info('连接关闭', e)
        // 连接关闭, 10s后自动尝试重新连接
        setTimeout(() => {
          isConnectAError && reConnect()
        }, ReconnectConfig.smallReConnectTime);
      });
    }
    // 连接成功回调函数
    this.websocket.onopen = function () {
      // 连接成功
      isConnect = true
      isConnecting = false;
      isConnectAError = false;
      Logger.info('连接成功')
      // 发送上线消息
      onlineMsg.data.onlineTime = pattern();
      sendLoginMsg(onlineMsg, Logger.warn);
      // 执行连接成功回调函数
      HtmWebSocket.LifeCycle.created().catch(err => {
        Logger.error(err);
      }).finally(() => {
        // 开启心跳检测
        heartConfig.start();
      });
    }
    // 连接发生错误的回调方法
    this.websocket.onerror = function () {
      Logger.error("WebSocket连接发生错误");
      //连接断开修改标识
      isConnect = false;
      //连接异常
      isConnectAError = true;
      //连接错误 需要重连
      reConnect();
    }
  }

  static getInstance = function (onlineMsg, ws) {
    if (HtmWs.isConFail()) {
      Logger.info("开始获取HtmWs单例");
      HtmWs.instance = new HtmWs(onlineMsg, ws);
      Logger.info("获取HtmWs单例成功");
    }
    return HtmWs.instance;
  }

  /**
   * 是否连接成功
   * @returns {boolean} 连接成功与否
   */
  static isConSuccess() {
    // 未初始化
    if (!HtmWs.isInit()) {
      return false;
    }
    // 非连接打开状态
    return HtmWs.instance.websocket.readyState === HtmWs.instance.websocket.OPEN;
  }

  /**
   * 是否连接中
   * @returns {boolean} 连接中与否
   */
  static isConnecting() {
    // 未初始化
    if (!HtmWs.isInit()) {
      return false;
    }
    // 非连接打开状态
    return HtmWs.instance.websocket.readyState === HtmWs.instance.websocket.CONNECTING;
  }

  /**
   * 是否连接失败
   * @returns {boolean} 连接失败与否
   */
  static isConFail() {
    // 未初始化
    if (!HtmWs.isInit()) {
      return true;
    }
    // 非连接打开状态
    return HtmWs.instance.websocket.readyState === HtmWs.instance.websocket.CLOSED ||
      HtmWs.instance.websocket.readyState === HtmWs.instance.websocket.CLOSING;
  }

  /**
   * 是否初始化
   * @returns {boolean} 初始化与否
   */
  static isInit() {
    // 未初始化
    if (!HtmWs.instance) {
      return false;
    }
    // 未创建连接
    return HtmWs.instance.websocket;
  }

  /**
   * 获取websocket连接
   * @returns {*}
   */
  static getWebsocket() {
    // 未初始化
    if (!HtmWs.isInit()) {
      return null;
    }
    return HtmWs.instance.websocket;
  }

  /**
   * 关闭连接
   */
  static closeWs(){
    // 未连接成功
    if(!HtmWs.isConSuccess()){
      return;
    }
    HtmWs.instance.websocket.close();
    HtmWs.instance = null;
  }

}
//===================================== 单例对象 结束 =====================================

export {
  HtmWebSocket
};
